<?php
/**
 * PDO数据库驱动类
 *
 * @author Mo yi <root@imoi.cn>
 * @link   http://www.imoi.cn
 * @copyright Copyright &copy; 2010-2012 DuoLam Software LLC
 * @license http://www.imoi.cn/license/
 * @version $Id: dpPdoDriver.php 2799 2012-04-10 09:31:13Z Mo.yi $
 * @package db.drivers
 * @since 1.0
 */

class dpPdoDriver extends dpComponent implements IModel
{

	protected $queryNum = 0;

	public    $connect  = array();

	protected $selfConnect;

	protected $prefix;

	protected $querySql = array();

	protected $exeTime;

	protected $_pdoStmt;

	protected $_pdoSth;

	protected $_stmtSql;

	public static $fetchType = array(
			'both'   => PDO::FETCH_BOTH,
			'column' => PDO::FETCH_COLUMN,
			'assoc'  => PDO::FETCH_ASSOC,
	);

	/**
	 * PDO初始化，连接数据库
	 * @param string $name   主从库连接名
	 * @param array  $config 数据库配置信息
	 */
	public function __construct($name,$config)
	{
		if ($config['type'] == 'sqlite')
			$dsn  = $config['type'].':'.$config['host'];
		else if ($config['type'] == 'sqlsrv')
			$dsn  = $config['type'].':Server='.$config['host'].';Database='.$config['dbname'].';';
		else if ($config['type'] == 'mssql' || $config['type'] == 'dblib')
			$dsn  = $config['type'].'host='.$config['host'].';dbname='.$config['dbname'].';';
		else {
			$dsn  = $config['type'].':host='.$config['host'].';dbname='.$config['dbname'].';';
			$dsn .= 'charset='.$config['charset'];
		}

		if (!isset($this->connect[$name])) {
			try {
				$this->connect[$name] = @new PDO($dsn,$config['username'],$config['passwd']);
			} catch(PDOException $e) {
				$msg['info'] = 'PDO连接数据库出错，驱动：'.$config['type'];
				$msg['msg']  = $e->getMessage();
				$this->halt($msg);
			}
		}

		$this->selfConnect = &$this->connect[$name];
		$this->prefix      = $config['prefix'];
	}

	public function __call($name,$param)
	{
		method_exists($this, $name) or $this->halt('调用的 '.$name.' 方法不存在');
	}

	public function getStmt()
	{
		return $this->_pdoStmt;
	}

	/**
	 * 执行SQL命令方法，如果是insert、delete、update操作则调用execute方法
	 * 非查询SQL命令返回影响记录的行数
	 * 是查询SQL命令返回资源句柄
	 * 每次调用该方法，统计查询数+1
	 * @param string $sql  SQL语句
	 * @return resource or int  资源类型或者影响记录行数的结果
	 */
	public function query($sql)
	{
		$this->queryNum++;
		if (!preg_match('/SELECT|SHOW|PRAGMA/i',$sql))
			return $this->execute($sql);
		else {
			$this->querySql[] = $this->getDealTablePrefix($sql);
			$start = microtime(true);
			$this->_pdoStmt = $this->selfConnect->query($this->getDealTablePrefix($sql));
			$this->exeTime[$this->getDealTablePrefix($sql)] = substr(microtime(true) - $start, 0, 8);
			if($this->_pdoStmt === false) $this->halt('SQL查询错误',$sql);
		}

	}

	/**
	 * 执行非SELECT操作接口，返回影响记录的行数
	 * @param string $sql
	 * @return int
	 */
	public function execute($sql)
	{
		$count = $this->selfConnect->exec($this->getDealTablePrefix($sql));

		if ($count === false or $count === null) $this->halt('SQL执行错误',$sql);

		if (preg_match('/INSERT/i',$sql))
			return $this->lastId();
		else
			return $count;
	}

	/**
	 * 返回页面查询的SQL语句
	 */
	public function getQuerySql()
	{
		return $this->querySql;
	}

	/**
	 * 返回最后insert之后的ID
	 */
	public function lastId()
	{
		return $this->selfConnect->lastInsertId();
	}

	/** 
	 * 获取表中的一行数据
	 * @param string $sql    SQL语句
	 * @param int    $expire 缓存有效期
	 * @param mixed  $mode   返回类型
	 * @return mixed 查询的数据集
	 */
	public function find($sql,$expire=-1,$mode='assoc')
	{
		if ($expire != -1) {
			$cache   = $this->getCache();
			$content = $cache->get($sql);

			if ($content)
				return $content;
			else {
				$this   -> query($sql);
				$result =  $this->unEscape($this->_pdoStmt->fetch(self::$fetchType[$mode]));
				$cache  -> set($sql,$result,$expire);
				return $result;
			}
		} else {
			$sourse = $this->query($sql);
			return $this->unEscape($this->_pdoStmt->fetch(self::$fetchType[$mode]));
		}
	}

	/** 
	 * 获取表中的多行数据
	 * @param string $sql    SQL语句
	 * @param int    $expire 缓存有效期
	 * @param mixed  $mode   返回类型
	 * @return mixed 查询的数据集
	 */
	public function finds($sql,$expire=-1,$mode='assoc')
	{
		if ($expire != -1) {
			$cache   = $this->getCache();
			$content = $cache->get($sql.'_all');
			if ($content)
				return $content;
			else {
				$this->query($sql);
				$data = $this->unEscape($this->_pdoStmt->fetchAll(self::$fetchType[$mode]));
				$cache->set($sql.'_all',$data,$expire);
				return $data;
			}
		} else {
			$this->query($sql);
			$data = $this->_pdoStmt->fetchAll(self::$fetchType[$mode]);
			return $this->unEscape($data);
		}
	}

	/**
	 * PDO驱动支持SQL预处理
	 * @param string $sql   带占位符的SQL语句
	 * @return object       类本身
	 */
	public function prepare($sql)
	{
		$sql = $this->getDealTablePrefix($sql);
		$this->_stmtSql = $sql;
		$this->_pdoSth  = $this->selfConnect->prepare($sql);
		return $this;
	}

	/**
	 * 预处理绑定参数
	 * @param array 参数列表
	 * @return object 类本身
	 */
	public function bindValue($array)
	{
		if (!is_object($this->_pdoSth)) return false;

		if (is_array($array)) {
			foreach ($array as $k => $v) {
				
					$this->_pdoSth->bindValue($k,$v,$this->getPdoType(gettype($v)));
			}
			return $this;
		} else
			return false;	
	}

	public function bindParam($array){}

	/**
	 * INSERT UPDATE等语句通过此接口进行预处理查询
	 * @return int 返回影响的行数或者最后插入的ID
	 */
	public function stmtQuery()
	{
		if (!is_object($this->_pdoSth)) return false;

		$this->queryNum++;

		if (preg_match('/UPDATE|DELETE/i',$this->_stmtSql)) {
			$this->_pdoSth->execute();
			return $this->_pdoSth->rowCount();
		} else if(preg_match('/INSERT/i',$this->_stmtSql)) {
			$this->_pdoSth->execute();
			return $this->lastId();
		} else {
			$this->_pdoSth->execute();
		}
	}

	/**
	 * 通过预处理获得一行结果集
	 * @param  int   $expire 缓存过期时间
	 * @return array 返回一行数据的结果集
	 */
	public function stmtFind($expire=-1)
	{
		if (!is_object($this->_pdoSth)) return false;

		$this->stmtQuery();

		if ($expire != -1) {
			$cache   = $this->getCache();
			$content = $cache->get($this->_stmtSql);

			if ($content)
				return $content;
			else {
				$result =  $this->unEscape($this->_pdoSth->fetch(self::$fetchType['assoc']));
				$cache  -> set($sql,$result,$expire);
				return $result;
			}

		} else {
			return $this->unEscape($this->_pdoSth->fetch(self::$fetchType['assoc']));
		}
	}

	/**
	 * 通过预处理获取多个结果集
	 * @param  int   $expire 缓存过期时间
	 * @return array 返回多行数据的结果集
	 */
	public function stmtFinds($expire=-1)
	{
		if (!is_object($this->_pdoSth)) return false;

		$this->stmtQuery();
		if ($expire != -1) {
			$cache   = $this->getCache();
			$content = $cache->get($this->_stmtSql.'_pdostmt');

			if ($content)
				return $content;
			else {
				$data  =  $this->unEscape($this->_pdoSth->fetchAll(self::$fetchType['assoc']));
				$cache -> set($this->_stmtSql.'_pdostmt',$data,$expire);
				return $data;
			}

		} else {
			$data = $this->_pdoSth->fetchAll(self::$fetchType['assoc']);
			return $this->unEscape($data);
		}
	}

	/**
	 * 开启事务
	 */
	public function startAffairs()
	{
		$this->selfConnect->beginTransaction();
	}

	/**
	 * 提交事务给数据库，并开启自动提交模式
	 */
	public function submitAffairs()
	{
		$this->selfConnect->commit();
	}

	/**
	 * 事务回滚，并开启自动提交模式
	 */
	public function rollbackAffairs()
	{
		$this->selfConnect->rollBack();
	}

	/**
	 * 返回统计行数
	 * 如果是sql语句的话，则进行查询，返回统计行数
	 * 如果是资源的话，直接返回统计行数
	 * @param string or resource  $sql SQL语句或资源
	 * @return int
	 */
	public function count($sql=null)
	{
		if (preg_match('/SELECT/i',$sql)) {
			$this->query($sql);
			return $this->_pdoStmt->rowCount();
		} else {
			if (is_object($this->_pdoSth))
				return $this->_pdoSth->rowCount();
			else
				return $this->_pdoStmt->rowCount();
		}
	}

	/**
	 * 对数据进行转义处理
	 * @param string or array $value 
	 * @return string or array
	 */
	public function escape($value)
	{
		if (is_array($value)) { 
		   return array_map(array($this, 'escape'), $value);
		} else {

		   if (get_magic_quotes_gpc()) {
			   $value = stripslashes($value);
		   }

		   if ( !is_numeric($value) || $value[0] == '0' ) {
			   $value = $this->selfConnect->quote(trim($value,"'"));
		   }

		   return $value;
		}
	}

	/**
	 * 对转义后的数据进行复原
	 * @param string or array $value
	 * @return string or array
	 */
	public function unEscape($value)
	{
		if (is_array($value)) {
			$data = array();

			foreach($value as $k => $v) {
				if (is_array($v)) {
					foreach($v as $k1 =>$v1) $data[$k][$k1] = $this->removeQuote(stripslashes($v1));
				} else
					$data[$k] = $this->removeQuote(stripslashes($v));
			}

			return $data;
		} else
			return $this->removeQuote(stripslashes($value));
	}

	/**
	 * 去掉前后单引号
	 */
	public function removeQuote($str)
	{
		if (preg_match("/^\'/",$str))
            $str = substr($str, 1, strlen($str) - 1);

		if (preg_match("/\'$/",$str))
			$str = substr($str, 0, strlen($str) - 1);
		
		$str = str_replace(array("\\\'","\\&quot;"), array("'",'"'), $str);
		return $str;
	}

	/**
	 * 返回当前页进行了多少次查询
	 */
	public function getQueryNum()
	{
		return $this->queryNum;
	}

	/**
	 * 返回每条SQL执行时间
	 */
	public function getSqlExeTime()
	{
		return $this->exeTime;
	}

	/**
	 * 获取对SQL语句表前缀处理后的SQL
	 * @param string $sql   SQL语句
	 * @return string 处理后的SQL语句
	 */
	public function getDealTablePrefix($sql)
	{
		return preg_replace('/\{(\w+)\}/i', $this->prefix.'\\1', $sql);
	}

	/**
	 * 获取缓存句柄
	 */
	public function getCache()
	{
		return D::app()->cache();
	}

	/**
	 * 获取当前连接数据库的句柄
	 * @return resource
	 */
	public function getConnect()
	{
		return $this->selfConnect;
	}

	/**
	 * 返回PDO预处理类型
	 * @param string $type  数据类型
	 * @param mixed         PDO 类型
	 */
	public function getPdoType($type)
	{
	    static $map=array(
	        'boolean'=>PDO::PARAM_BOOL,
	        'integer'=>PDO::PARAM_INT,
	        'string' =>PDO::PARAM_STR,
	        'NULL'   =>PDO::PARAM_NULL,
	    );
	    return isset($map[$type]) ? $map[$type] : PDO::PARAM_STR;
	}

	/**
	 * 错误控制台
	 * @param string $msg  错误信息
	 * @param string $sql  错误的SQL语句
	 */
	public function halt($msg, $sql=null)
	{
		$info  = !is_object($this->selfConnect)
				 ? $msg['msg']
				 : $this->selfConnect->errorInfo();
		$code  = !is_object($this->selfConnect)
				 ? null
				 : $this->selfConnect->errorCode();
		$info  = is_array($info) ? 'SQL STATE['.$info[0].'] ['.$info[1].'] '.$info[2] : $info;
		$msg   = is_array($msg)  ? $msg['info'] : $msg;
		$info  = !dpText::is_utf8($info) ? iconv('gbk','utf-8',$info) : $info;
 		$infos = array(
			'sql'       => $this->getDealTablePrefix($sql),
			'errorInfo' => $info,
			'errorCode' => $code
		);
		dpException::error($msg.'，dpPdoDriver',true,$infos);
	}

	/**
	 * 关闭连接数据库
	 * 关闭所有连接数据库的句柄，并重置初始值
	 */
	public function close()
	{
		$this->selfConnect = null;
		$this->connect     = array();
	}

	/**
	 * 析构方法，在释放内存的时候，执行关闭数据库连接
	 */
	public function __destruct()
	{
		$this->close();
	}

}
?>